package fiuba.algo3;

import java.io.File;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Document;
import org.w3c.dom.Element;


public class Juego {
	private Caso casoActual;
	private Policia policia;
	private Mapa mapa;
	private Ciudad proximaCiudad;
	private Ladron ladron;
	int indiceCiudadActual;
	private Edificio edificioAVisitar;
	private ArrayList<Edificio> edificiosActuales;
	private boolean tiempoCumplido;
	private boolean ladronEscapo_conOrden;
	private boolean ladronEscapo_sinOrden;
	private boolean ladronArrestado;
	
	public Juego() {
		this.policia = new Policia();
		this.indiceCiudadActual = 0;
	}
	
	public Element serializar(Document doc) {
		Element elementoJuego = doc.createElement("Juego");
		elementoJuego.appendChild(this.casoActual.serializar(doc));
		elementoJuego.appendChild(this.policia.serializar(doc));
		elementoJuego.appendChild(this.proximaCiudad.serializar(doc));	
		elementoJuego.appendChild(this.ladron.serializar(doc));
		elementoJuego.appendChild(this.mapa.serializar(doc));
		return elementoJuego;
	}
	
	public static Juego hidratar(Document doc) {
		Juego nuevoJuego = new Juego();
		Element elementoJuego = (Element)doc.getElementsByTagName("Juego").item(0);
		Caso  nuevoCaso = Caso.hidratar( elementoJuego.getElementsByTagName("CasoActual").item(0) );
		nuevoJuego.casoActual = nuevoCaso;
		Policia  nuevoPolicia = Policia.hidratar( elementoJuego.getElementsByTagName("Policia").item(0) );
		nuevoJuego.policia = nuevoPolicia;
		Ciudad  nuevaCiudad = Ciudad.hidratar( elementoJuego.getElementsByTagName("Ciudad").item(0) );
		nuevoJuego.proximaCiudad = nuevaCiudad;
		Ladron nuevoLadron = Ladron.hidratar( elementoJuego.getElementsByTagName("Ladron").item(0) );
		nuevoJuego.ladron = nuevoLadron;
		Mapa nuevoMapa = Mapa.hidratar( elementoJuego.getElementsByTagName("Mapa").item(0) );
		nuevoJuego.mapa = nuevoMapa;
		return nuevoJuego;
	}
	 
	public boolean existePartida(String nombre) {
		File archivo = new File(nombre + ".xml");
		if ( archivo.exists() )
			return true;
		return false;
	}
	
	public void guardarPartida() throws Exception {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();
		
		Element juegoSerializardo = this.serializar(doc);
		
		doc.appendChild(juegoSerializardo);
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(doc);
		
		String nombreArchivo = this.getPolicia().getNombre() + ".xml";
		
		File archivoDestino = new File(nombreArchivo);
		StreamResult result = new StreamResult(archivoDestino);
		transformer.transform(source, result);
	}
	
	public static Juego cargarPartida(String nombreArchivo) throws Exception {
		File archivo = new File(nombreArchivo);
		
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();
		
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		doc = dBuilder.parse(archivo);
		doc.getDocumentElement().normalize();

		return Juego.hidratar(doc);
	}
	
	// GETTERS
	public Policia getPolicia() {
		return this.policia;
	}
	
	public Caso getCaso() {
		return this.casoActual;
	}
	
	public Ciudad getProximaCiudad() {
		return this.mapa.getProximaCiudad(this.policia.getCiudadActual());
	}
	
	public Edificio getEdificioAVisitar() {
		return this.edificioAVisitar;
	}
	
	public Ladron getLadron() {
		return this.ladron;
	}
	
	public Mapa getMapa() {
		return this.mapa;
	}
	// FIN GETTERS
	
	// SETTERS
	public void asignarPoliciaParaPruebas(Policia policiaFake) {
		this.policia = policiaFake;
	}
	
	public void setEdificioAVisitar(Edificio unEdificio) {
		this.edificioAVisitar = unEdificio;
	}
	
	public void setProximaCiudad() {
		this.proximaCiudad = this.mapa.getProximaCiudad(this.policia.getCiudadActual());
	}
	
	public void setNombrePolicia(String nombre) {
		this.policia.setNombre(nombre);
	}
	// FIN SETTERS
	
	public void generarCaso() {
		this.casoActual = this.policia.recibirCaso();
		this.ladron = this.casoActual.getLadron();
		this.mapa = new Mapa(this.ladron.getRutaDeEscape(), this.policia.getCiudadActual());
		this.proximaCiudad = this.mapa.getProximaCiudad(this.policia.getCiudadActual());
		this.tiempoCumplido = false;
		this.ladronEscapo_conOrden = false;
		this.ladronEscapo_sinOrden = false;
		this.ladronArrestado = false;
	}
	
	private void verificarTiempoCumplido() {
		this.tiempoCumplido = this.policia.tiempoCumplido();
	}
	
	public boolean casoPerdido() {
		return (this.tiempoCumplido || this.ladronEscapo_sinOrden || this.ladronEscapo_conOrden);
	}
	
	public boolean casoGanado() {
		return this.ladronArrestado;
	}
	
	// USUARIO
	public ArrayList<Ciudad> ofrecerOpcionesCiudadAVisitar() {
		ArrayList<Ciudad> tresCiudades = this.mapa.generarMapa();
		return tresCiudades;
	}
	
	public ArrayList<Edificio> ofrecerOpcionesEdificioAVisitar() {
		Banco banco = null;
		Aeropuerto aeropuerto = null;
		Biblioteca biblioteca = null;
		if (this.mapa.getProximaCiudad(this.policia.getCiudadActual()) != null){
			banco = new Banco(this.mapa.getProximaCiudad(this.policia.getCiudadActual()));
			aeropuerto = new Aeropuerto(this.mapa.getProximaCiudad(this.policia.getCiudadActual()));
			biblioteca = new Biblioteca(this.mapa.getProximaCiudad(this.policia.getCiudadActual()));
		}
		else{
			banco = new Banco(this.mapa.proximaCiudad());
			aeropuerto = new Aeropuerto(this.mapa.proximaCiudad());
			biblioteca = new Biblioteca(this.mapa.proximaCiudad());
		}
		
		ArrayList<Edificio> edificios = new ArrayList<Edificio>();
		edificios.add(banco);
		edificios.add(aeropuerto);
		edificios.add(biblioteca);
		
		this.edificiosActuales = edificios;
		if ( this.mapa.esUltimaCiudad( this.policia.getCiudadActual() ) )
			this.ladron.esconderse( this.edificiosActuales );
		
		return edificios;
	}
	
	private boolean usuarioAcertoCiudad(Ciudad ciudadUsuario) {
		return this.mapa.acertoCiudad(ciudadUsuario);
	}
	
	public boolean ladronEscapoConOrden() {
		return this.ladronEscapo_conOrden;
	}
	// FIN USUARIO
	
	// ACCIONES POLICIA
	public Pista policiaViaja(Ciudad ciudadUsuario) {
		this.policia.viajarA(ciudadUsuario);
		if (this.mapa.acertoCiudad(ciudadUsuario)){
			if (this.mapa.ciudadRecibePistaLadron()){
				Pista pista = this.obtenerPistaSobreElLadron();
				return pista;
			}
			else{
				PistaAviso pista = new PistaAviso();
				return pista;
			}
		}
		verificarTiempoCumplido();
		return null;
	}
		
	public boolean policiaVisitaEdificio() {
		this.policia.visitarEdificio(this.edificioAVisitar);
		if (this.edificioAVisitar.estaLadronEscondido()){
			policiaEncuentraAlLadron();
			return true;
		}
		verificarTiempoCumplido();
		this.policia.verificarSiDebeDormir();
		return false;
	}
	
	public boolean policiaEmiteOrdenDeArresto() {
		boolean resultado = this.policia.emitirOrdenDeArresto();
		verificarTiempoCumplido();
		return resultado;
	}
	
	public Pista policiaObtienePista() {
		// ciudad no es correcta? si no es correcta no doy informacion.
		if ( this.usuarioAcertoCiudad(this.policia.getCiudadActual()) == false){
			policiaVisitaEdificio();
		}

		// es la primer ciudad?
		if (this.policia.getCiudadActual().esIgualA(this.casoActual.getCiudadInicial())){
			policiaVisitaEdificio();

			return this.policia.recibirPista(this.edificioAVisitar);
		}
		// me fijo si es la ciudad donde esta el ladron
		if ( this.mapa.esUltimaCiudad( this.policia.getCiudadActual() ) ){
			if (policiaVisitaEdificio()){
				return null;
			}

		// es la ciudad, no doy pistas concretas, hiero al policia
			policiaEsHerido();
			PistaLadronCerca pista = new PistaLadronCerca();
			return pista;
		}
		
		// no es la ultima ciudad, recibo pista normalmente
		
		// ciudad correcta! debo entregar pista sobre el ladron
		policiaVisitaEdificio();
		return this.policia.recibirPista(this.edificioAVisitar);
	}
	
	private Pista obtenerPistaSobreElLadron() {
		Pista pista = new PistaLadron(this.ladron, this.mapa.obtenerPosicionEnLaRutaDeEscape());
		return pista;
	}
	
	private boolean policiaEncuentraAlLadron() {
		if ( this.policia.tieneOrdenDeArresto() ){
			if (this.policia.getOrdenDeArresto().getNombre().equals(this.ladron.getNombre())){
				this.policia.efectuarArresto();
				this.ladronArrestado = true;
				return true;
			}
			else{
				this.ladronEscapo_conOrden = true;
				return false;
			}
		}
		this.ladronEscapo_sinOrden = true;		
		return false;
	}
		
	private void policiaEsHerido() {
		int x = new Double(Math.random() * 2).intValue();
		Arma arma = null;
		switch (x){
		case 0:	arma = new Cuchillo(); break;
		case 1:	arma = new ArmaDeFuego(); break;
		}
		this.policia.recibirHerida(arma);
		verificarTiempoCumplido();
	}
	// FIN ACCIONES POLICIA

}
